﻿/******************************************************
 *     Torrent Zip File C# Version                    *
 *     Contact: gordon@romvault.com                   *
 *                                                    *
 *     This code is published to expand the           *
 *     availability and usage of torrent zip files.   *
 *     Please feel free to use this code or just      *
 *     learn what you need from it to complete your   *
 *     own torrent zip compatible applications.       *
 *                                                    *
 *     Just 2 things I ask, Just give me a little     *
 *     Credit in your code somewhere,                 *
 *     (Or just leave these comments attached)        *
 *     And Also please let me know if you are using   *
 *     and finding this code usefull in your project  *
 *     Chances are I will help you along the way.     *
 *                                                    *
 ******************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Ionic.Crc;

namespace TrrntzipDN
{
    [Flags]
    public enum TrrntZipStatus
    {
        Unknown = 0,
        ValidTrrntzip = 1,
        CorruptZip = 2,
        NotTrrntzipped = 4,
        BadDirectorySeparator = 8,
        Unsorted = 16,
        ExtraDirectoryEnteries = 32,
        RepeatFilesFound = 64
    }

    class ProcessZip
    {

        class ZippedFile
        {
            public int Index;
            public string Name;
            public ulong Size;
            public uint CRC;

            public byte[] ByteCRC
            {
                get
                {
                    byte[] bcrc = new byte[4];
                    bcrc[3] = (byte)(CRC & 0xff);
                    bcrc[2] = (byte)((CRC >> 8) & 0xff);
                    bcrc[1] = (byte)((CRC >> 16) & 0xff);
                    bcrc[0] = (byte)((CRC >> 24) & 0xff);
                    return bcrc;
                }
                set
                {
                    CRC = (uint)value[3] +
                        ((uint)value[2] << 8) +
                        ((uint)value[1] << 16) +
                        ((uint)value[0] << 24);
                }
            }
        }

        private ZipFile _zipFile;
        private List<ZippedFile> _zippedFiles;


        private const int BufferSize = 1024 * 1024;
        private readonly byte[] _buffer;

        public ProcessZip()
        {
            _buffer = new byte[BufferSize];
        }

        public void ProcessDir(string dirName)
        {
            Console.WriteLine("Checking Dir : " + dirName);
            Stopwatch stopwatch = new Stopwatch();
            int zipFileCount = 0;
            int zipFixedFiles = 0;

            stopwatch.Reset();
            stopwatch.Start();

            DirectoryInfo di = new DirectoryInfo(dirName);
            FileInfo[] fi = di.GetFiles();
            for (int i = 0; i < fi.Length; i++)
            {
                string filename = fi[i].FullName;
                string extention = Path.GetExtension(filename);
                if (!String.IsNullOrEmpty(extention) && extention.ToLower() == ".zip")
                {
                    zipFileCount++;
                    if (Process(fi[i]))
                        zipFixedFiles++;
                }
            }
            stopwatch.Stop();
            if (zipFileCount > 0)
            {
                Console.WriteLine("Executon time " + stopwatch.Elapsed.Hours + " hours " + stopwatch.Elapsed.Minutes % 60 + " mins " + stopwatch.Elapsed.Seconds % 60 + " secs");
                Console.WriteLine("Checked " + zipFileCount + " zip files.");
                Console.WriteLine("  " + (zipFileCount - zipFixedFiles) + " files were already up to date.");
                Console.WriteLine("");
            }

            if (Program.NoRecursion)
                return;

            string[] directories = Directory.GetDirectories(dirName);
            for (int i = 0; i < directories.Length; i++)
                ProcessDir(directories[i]);
        }

        public bool Process(FileInfo fi)
        {
            TrrntZipStatus tzs = OpenZip(fi);
            if (tzs != TrrntZipStatus.Unknown)
            {
                Console.WriteLine("Fail to Open : " + fi.Name);
                Console.WriteLine("");
                return false;
            }
            tzs = TrrntzipCheckZip();
            if (tzs != TrrntZipStatus.ValidTrrntzip || Program.ForceReZip)
            {
                tzs = ReZipFiles();
                if (tzs != TrrntZipStatus.ValidTrrntzip)
                    Console.WriteLine("Error Fixing - " + fi.Name);
                return (tzs == TrrntZipStatus.ValidTrrntzip);
            }

            Console.WriteLine("Skipping - " + fi.Name);
            Console.WriteLine("");
            return false;

        }

        private TrrntZipStatus OpenZip(FileInfo fi)
        {
            _zipFile = new ZipFile();
            _zippedFiles = new List<ZippedFile>();

            ZipReturn zr = _zipFile.ZipFileOpen(fi.FullName, fi.LastWriteTime.Ticks, false);
            if (zr != ZipReturn.ZipGood)
                return TrrntZipStatus.CorruptZip;

            for (int i = 0; i < _zipFile.LocalFilesCount(); i++)
            {
                _zippedFiles.Add(
                        new ZippedFile
                        {
                            Index = i,
                            Name = _zipFile.Filename(i),
                            ByteCRC = _zipFile.CRC32(i),
                            Size = _zipFile.UncompressedSize(i)
                        }
                    );
            }
            return TrrntZipStatus.Unknown;
        }

        private TrrntZipStatus TrrntzipCheckZip()
        {
            TrrntZipStatus tzStatus = TrrntZipStatus.Unknown;

            // first check if the file is a trrntip files
            if (_zipFile.ZipStatus != ZipStatus.TrrntZip)
                tzStatus |= TrrntZipStatus.NotTrrntzipped;

            // check if any '/' = 47 need converted to '\' = 92
            // this needs done before the sort, so that the sort is correct.
            foreach (ZippedFile t in _zippedFiles)
            {
                char[] bytes = t.Name.ToCharArray();
                bool fixDir = false;
                for (int j = 0; j < bytes.Length; j++)
                {
                    if (bytes[j] != 47) continue;
                    fixDir = true;
                    bytes[j] = (char)92;
                    tzStatus |= TrrntZipStatus.BadDirectorySeparator;
                }
                if (fixDir) t.Name = new string(bytes);
            }


            // next check if the zip files are sorted correctly
            bool thisSortFound = true;
            while (thisSortFound)
            {
                thisSortFound = false;
                for (int i = 0; i < _zippedFiles.Count - 1; i++)
                {
                    int c = TrrntZipStringCompare(_zippedFiles[i].Name, _zippedFiles[i + 1].Name);
                    if (c > 0)
                    {
                        ZippedFile T = _zippedFiles[i];
                        _zippedFiles[i] = _zippedFiles[i + 1];
                        _zippedFiles[i + 1] = T;

                        tzStatus |= TrrntZipStatus.Unsorted;
                        thisSortFound = true;
                    }
                }
            }

            // need to check for directories to remove
            for (int i = 0; i < _zippedFiles.Count - 1; i++)
            {
                // check if this is a directory entry
                if (_zippedFiles[i].Name[_zippedFiles[i].Name.Length - 1] != 92)
                    continue;

                // check if the next filename is shorter or equal to this filename.
                // if it is shorter or equal it cannot be in the sub directory.
                if (_zippedFiles[i + 1].Name.Length <= _zippedFiles[i].Name.Length)
                    continue;

                bool delete = true;
                for (int j = 0; j < _zippedFiles[i].Name.Length; j++)
                {
                    if (_zippedFiles[i].Name[j] != _zippedFiles[i + 1].Name[j])
                    {
                        delete = false;
                        break;
                    }
                }
                if (delete)
                {
                    _zippedFiles.RemoveAt(i);
                    tzStatus |= TrrntZipStatus.ExtraDirectoryEnteries;
                    i--;
                }
            }

            // check for repeat files
            for (int i = 0; i < _zippedFiles.Count - 1; i++)
            {
                if (_zippedFiles[i].Name == _zippedFiles[i + 1].Name)
                    tzStatus |= TrrntZipStatus.RepeatFilesFound;
            }


            // if nothing was found wrong with the zip set it to valid trrntzip file
            if (tzStatus == TrrntZipStatus.Unknown)
                tzStatus = TrrntZipStatus.ValidTrrntzip;

            return tzStatus;
        }

        private static int TrrntZipStringCompare(string string1, string string2)
        {
            char[] bytes1 = string1.ToCharArray();
            char[] bytes2 = string2.ToCharArray();

            int pos1 = 0;
            int pos2 = 0;

            for (; ; )
            {
                if (pos1 == bytes1.Length)
                    return ((pos2 == bytes2.Length) ? 0 : -1);
                if (pos2 == bytes2.Length)
                    return 1;

                int byte1 = bytes1[pos1++];
                int byte2 = bytes2[pos2++];

                if (byte1 >= 65 && byte1 <= 90) byte1 += 0x20;
                if (byte2 >= 65 && byte2 <= 90) byte2 += 0x20;

                if (byte1 < byte2)
                    return -1;
                if (byte1 > byte2)
                    return 1;
            }
        }


        private TrrntZipStatus ReZipFiles()
        {
            ulong totalSize = 0;
            string filename = _zipFile.ZipFilename;

            Console.WriteLine("Rezipping - " + Path.GetFileName(filename));
            Console.WriteLine("");
            Console.WriteLine("-------------------------------------------------------------------------");
            Console.WriteLine("");

            string dir = Path.GetDirectoryName(filename);
            string tmpFilename = (String.IsNullOrEmpty(dir)) ? "_trrntzip.tmp" : Path.Combine(dir, "_trrntzip.tmp");

            if (File.Exists(tmpFilename))
                File.Delete(tmpFilename);

            ZipFile zipFileOut = new ZipFile();
            zipFileOut.ZipFileCreate(tmpFilename);

            // by now the _zippedFiles have been sorted so just loop over them
            for (int i = 0; i < _zippedFiles.Count; i++)
            {
                ZippedFile t = _zippedFiles[i];
                Console.Write("Adding - " + t.Name + " (" + t.Size + " bytes)");

                Stream readStream;
                ulong streamSize;
                ushort compMethod;
                ZipReturn zrInput = _zipFile.ZipFileOpenReadStream(t.Index, false, out readStream, out streamSize, out compMethod);



                Stream writeStream;
                ZipReturn zrOutput = zipFileOut.ZipFileOpenWriteStream(false, true, t.Name, streamSize, 8, out writeStream);

                totalSize += t.Size;

                if (zrInput != ZipReturn.ZipGood || zrOutput != ZipReturn.ZipGood)
                {
                    //Error writing local File.
                    zipFileOut.ZipFileClose();
                    _zipFile.ZipFileClose();
                    File.Delete(tmpFilename);
                    return TrrntZipStatus.CorruptZip;
                }

                Stream crcCs = new CrcCalculatorStream(readStream, true);

                ulong sizetogo = streamSize;

                while (sizetogo > 0)
                {
                    Console.Write(".");
                    int sizenow = sizetogo > BufferSize ? BufferSize : (int)sizetogo;

                    crcCs.Read(_buffer, 0, sizenow);
                    writeStream.Write(_buffer, 0, sizenow);
                    sizetogo = sizetogo - (ulong)sizenow;
                }
                writeStream.Flush();

                crcCs.Close();
                _zipFile.ZipFileCloseReadStream();


                Console.WriteLine("Done");
                Console.WriteLine("");

                uint crc = (uint)((CrcCalculatorStream)crcCs).Crc;

                if (crc != t.CRC)
                {
                    // error in crc
                    Console.WriteLine("File is Corrupt!!");
                }

                zipFileOut.ZipFileCloseWriteStream(t.ByteCRC);
            }

            zipFileOut.ZipFileClose();
            _zipFile.ZipFileClose();
            File.Delete(filename);
            File.Move(tmpFilename, filename);

            Console.WriteLine("-------------------------------------------------------------------------");
            Console.WriteLine("");
            Console.WriteLine("Rezipped " + _zippedFiles.Count + " compressed files totaling " + totalSize + " bytes.");
            Console.WriteLine("");
            Console.WriteLine("--");
            Console.WriteLine("");


            return TrrntZipStatus.ValidTrrntzip;
        }
    }
}
